﻿using System;
using System.Collections.Generic;

namespace Ndo.Maths
{
    public class XY<XType, YType>
    {
        public XType X;
        public YType Y;
        public int GenerationNumber = -1;
    }
    public class EA<Individual, XType, YType>
        where Individual : XY<XType, YType>, new()
    {

        public delegate YType Function(XType x);
        public delegate ICollection<XType> InitializationOperator(int count);
        public delegate Individual PickOperator(IList<Individual> inds);
        public delegate XType CrossoverOperator(XType x1, XType x2);
        public delegate XType MutationOperator(XType x1);
        public delegate IList<Individual> SelectionOperator(IList<Individual> inds, int limit);

        public Function F;
        public InitializationOperator Initialization;
        public PickOperator Pick;
        public CrossoverOperator Crossover;
        public MutationOperator Mutation;
        public SelectionOperator Selection;

        public EA()
        {
            Selection = SortingSelection;
        }


        private List<Individual> population = new List<Individual>();
        private int generationNumber = -1;
        public int ScaleLimit = 100;
        public double CrossoverRate = 0.2;
        public double MutationRate = 0.02;
        public IList<Individual> Population { get { return population.AsReadOnly(); } }
        public int Scale { get { return population.Count; } }
        public int GenerationNumber { get { return generationNumber; } }
        protected int Rate2Count(double r)
        {
            int a = (int) Math.Round(population.Count*r);
            if (a == 0)
                return a + 1;
            return a;
        }
        protected virtual IEnumerable<Pair<XType>> SelectForCrossover()
        {
            if (Pick == null)
                yield break;
            for (int i = Rate2Count(CrossoverRate); i > 0; i--)
            {
                yield return new Pair<XType>(Pick(population).X, Pick(population).X);
            }
        }
        protected virtual IEnumerable<XType> SelectForMutation()
        {
            if (Pick == null)
                yield break;
            for (int i = Rate2Count(MutationRate); i > 0; i--)
            {
                yield return Pick(population).X;
            }
        }

        protected virtual Individual Generate(XType x)
        {
            Individual i = new Individual();
            i.X = x;
            if(F != null)
                i.Y = F(x);
            i.GenerationNumber = generationNumber;
            return i;
        }

        public virtual void Preprocess()
        {
            lock (this)
            {
                population.Clear();
                generationNumber = 0;
                foreach (XType x in Initialization(ScaleLimit))
                {
                    population.Add(Generate(x));
                }
            }
            if(PopulationChanged!=null)
                PopulationChanged(this);
        }
        public virtual void Process()
        {
            lock(this)
            {
                generationNumber++;
                List<Individual> list = new List<Individual>(population);
                if(Crossover != null)
                    foreach (Pair<XType> pair in SelectForCrossover())
                    {
                        list.Add(Generate(Crossover(pair.A, pair.B)));
                    }
                if (Mutation != null)
                    foreach (XType x in SelectForMutation())
                    {
                        list.Add(Generate(Mutation(x)));
                    }
                population.Clear();
                population.AddRange(Selection(list, ScaleLimit));
            }
            if (PopulationChanged != null)
                PopulationChanged(this);
        }

        public virtual bool Converged
        {
            get { return false; }
        }

        public event System.Action<EA<Individual, XType, YType>> PopulationChanged;
        public void Evolve(int nGen)
        {
            Preprocess();
            while(generationNumber < nGen && !Converged)
            {
                Process();
            }
        }

        public static IList<Individual> SortingSelection(IList<Individual> inds, int limit)
        {
            if (limit > 0)
            {
                List<Individual> list = new List<Individual>(inds);
                list.Sort();
                Individual[] newGen = new Individual[limit];
                list.CopyTo(0, newGen, 0, limit);
                return newGen;
            }
            return inds;
        }

        public delegate Value FitnessEvalutator<Individual, Value>(Individual ind) where Value : IComparable<Value>;
        public static SelectionOperator FitnessEvalutationSelection<Value>(FitnessEvalutator<Individual, Value> fe)
             where Value : IComparable<Value>
        {
            return new FE<Value>(fe).Selection;
        }

        private class FE<Value>
            where Value : IComparable<Value>
        {
            private FitnessEvalutator<Individual, Value> fe;
            public FE(FitnessEvalutator<Individual, Value> fe)
            {
                this.fe = fe;
            }

            public IList<Individual> Selection(IList<Individual> inds, int limit)
            {
                if(limit>=inds.Count)
                    return new List<Individual>(inds);
                Value[] values = new Value[inds.Count];
                Individual[] keys = new Individual[values.Length];
                for (int i = 0; i < values.Length; i++)
                    values[i] = fe(keys[i]);
                Array.Sort(values, keys);
                Individual[] ret = new Individual[limit];
                Array.Copy(keys, 0, ret, 0, limit);
                return ret;
            }
        }
        private class Picker
        {
            private Random rand = new Random();
            public Individual BiasPick(IList<Individual> inds)
            {
                double d = rand.NextDouble();
                int n = inds.Count;
                return inds[(int)(n * d * d)];
            }
            public Individual RandomPick(IList<Individual> inds)
            {
                return inds[rand.Next(inds.Count)];
            }
        }

        public static PickOperator CreateBiasPicker()
        {
            return new Picker().BiasPick;
        }

        public static PickOperator CreateRandomPicker()
        {
            return new Picker().RandomPick;
        }

        public static IEnumerable<XType> EnumerateXs(IEnumerable<Individual> inds)
        {
            foreach (Individual ind in inds)
            {
                yield return ind.X;
            }
        }

        public static IEnumerable<YType> EnumerateYs(IEnumerable<Individual> inds)
        {
            foreach (Individual ind in inds)
            {
                yield return ind.Y;
            }
        }
    }
}
